import Proj from "proj4";

export interface FourParams {
  x_0: number; // east false
  y_0: number; // north false
  t: number; // rotation
  k: number; // scale
}

export interface ProjParams {
  proj?: string;
  lat_0?: number;
  lon_0?: number;
  k?: number;
  x_0: number;
  y_0?: number;
  ellps?: string;
  units?: string;
}

const bjGeosString = "+proj=longlat +ellps=krass +no_defs";
const wgsGeosString = "+proj=longlat +datum=WGS84 +no_defs";

/**
 * cocal cartography's coordinates to beijing 54's projection coordinates
 * @param coord source coordinates
 * @param params four parameters
 * @param backward forward / backward
 */
export function localproj_bjproj(
  coordinates: [number, number] | Array<[number, number]>,
  params: FourParams,
  backward?: boolean
) {
  if (!coordinates) throw new Error("input coordinate error!");
  if (!params) throw new Error("four parameter error");

  let normCoordinates: Array<[number, number]> = coordinates as Array<
    [number, number]
  >;
  let resCoordinates: Array<[number, number]> = [];
  if (Object.prototype.toString.call(coordinates[0]) != "[object Array]") {
    normCoordinates = [coordinates as any];
  }

  // traverse coordinates
  normCoordinates.forEach((coordinate) => {
    const x = coordinate[1];
    const y = coordinate[0];

    const x_0 = params.x_0;
    const y_0 = params.y_0;
    const t = params.t;
    const k = params.k;

    let x2, y2;
    if (!backward) {
      y2 = x_0 + x * k * Math.cos(t) - y * k * Math.sin(t);
      x2 = y_0 + y * k * Math.cos(t) + x * k * Math.sin(t);
    } else {
      x2 =
        ((x - x_0) * Math.cos(t) + (y - y_0) * Math.sin(t)) / k;
      y2 =
        ((y - y_0) * Math.cos(t) - (x - x_0) * Math.sin(t)) / k;
    }
    resCoordinates.push([x2, y2]);
  });

  // two return
  if (resCoordinates.length === 1) {
    return resCoordinates[0];
  } else {
    return resCoordinates;
  }
}

/**
 *  beijing54's projection coordinates to beijing54's geographic coordinates
 */
export function bjproj_bjlnglat(
  coordinates: [number, number] | Array<[number, number]>,
  projParams: ProjParams,
  backward?: boolean
) {
  if (!coordinates) throw new Error("input coordinate error!");
  if (!projParams) throw new Error("projection parameter error");

  // resolve proj4strings
  const localProjString = `+proj=${projParams.proj || "tmerc"} +lat_0=${
    projParams.lat_0 || "0"
  } +lon_0=${projParams.lon_0} +k=${projParams.k || "1"} +x_0=${
    projParams.x_0 || "500000"
  } +y_0=${projParams.y_0 || "0"} +ellps=${
    projParams.ellps || "krass"
  } +units=${projParams.units || "m"} +no_defs`;

  let normCoordinates: Array<[number, number]> = coordinates as Array<
    [number, number]
  >;
  let resCoordinates: Array<number[]> = [];
  if (Object.prototype.toString.call(coordinates[0]) != "[object Array]") {
    normCoordinates = [coordinates as any];
  }

  // tranverse coordiantes
  normCoordinates.forEach((coordinate) => {
    let resCoord;
    if (!backward) {
      resCoord = Proj(localProjString, bjGeosString, coordinate);
    } else {
      resCoord = Proj(bjGeosString, localProjString, coordinate);
    }
    resCoordinates.push(resCoord);
  });

  // two return
  if (resCoordinates.length === 1) {
    return resCoordinates[0];
  } else {
    return resCoordinates;
  }
}

/**
 * beijing54 geographic coordinates => WGS84 geographic coordinates
 */
export function bjlnglat_wgslnglat(
  coordinates: [number, number] | Array<[number, number]>,
  backward?: boolean
) {
  if (!coordinates) throw new Error("input coordinate error!");

  let normCoordinates: Array<[number, number]> = coordinates as Array<
    [number, number]
  >;
  let resCoordinates: Array<number[]> = [];
  if (Object.prototype.toString.call(coordinates[0]) != "[object Array]") {
    normCoordinates = [coordinates as any];
  }

  // tranverse coordiantes
  normCoordinates.forEach((coordinate) => {
    let resCoord;
    if (!backward) {
      resCoord = Proj(bjGeosString, wgsGeosString, coordinate);
    } else {
      resCoord = Proj(wgsGeosString, bjGeosString, coordinate);
    }
    resCoordinates.push(resCoord);
  });

  // two return
  if (resCoordinates.length === 1) {
    return resCoordinates[0];
  } else {
    return resCoordinates;
  }
}
